﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VuzixSample.model.snes.starfox;
using System.Diagnostics;
using Microsoft.Xna.Framework;

 /* Gareth Williams
  *  TODO: look at increments - they need rethinking for consistency
  *  22/04/2012 - refactored / broken down parsing functionality - increments follow a general system.
  *  TODO: palette class is a bit ridiculous at the moment - consider revising into singleton or similar...
 */

namespace VuzixSample.controller.snes.starfox
{
    class StarfoxParser : RomParser
    {
        public enum VertexListTypes { NORMAL, MIRRORED, ANIMATED, WIREFRAME_EXPERIMENTAL };

        //there are many pallette types in starfox including textures
        //this marks the beginning of 20 pallettes of 32 colours 
        //where each colour is a 4bit value - 1.5 bytes per colour
        private const uint PALLETTE_DATA_OFFSET = 0x18D8A;

        private const Byte END_OF_VERTICES_FLAG = 0x0C;
        private const Byte BEGIN_TRIANGLES_FLAG = 0x30;
        private const Byte BEGIN_BSP_FLAG = 0x3C;
        private const Byte BEGIN_FACE_GROUP = 0x14;
        private const Byte END_OF_FACE_GROUP = 0xFF;

        private const Byte BRANCH_NODE = 0x28;
        private const Byte LEAF_NODE = 0x44;

        private const Byte MIRROR_X_VERT_LIST = 0x38;
        private const Byte NORMAL_VERT_LIST = 0x04;

        //TODO:factory interface for retrieving models
        //and dispose method to release data.
        public List<StarFoxGeometry> polyModels;

        protected override void parse()
        {
            //indexed colors
            parsePalletteData();

            //Geometry
            if (polyModels == null) polyModels = new List<StarFoxGeometry>();
            else polyModels.Clear();

            //just get the arwing for now
            StarFoxGeometry arwing = new StarFoxGeometry();

            int index = (int)OBJECT_DATA_OFFSET;

            index = parseVertices(index, arwing);
            index = parseTriangles(index, arwing);
            index = parseFacesFromBSP(index, arwing);

            polyModels.Add(arwing);

            Debug.WriteLine("complete parse hole");
        }
        
        #region helpers
        private void parsePalletteData()
        {
            StarFoxPallettes.pallettes = new Color[20,32];
            int index = (int)PALLETTE_DATA_OFFSET;

            for (int p = 0; p < 20; p++)
            {
                //read two 4 bit colour triads at a time (3 bytes) to observe byte boundaries
                for (int c = 0; c < 32; c += 2)
                {
                    uint twoColours = BitConverter.ToUInt32(rom.romData, index);

                    //extract rgb from each triad
                    Color colorOne = new Color();
                    Color colorTwo = new Color();

                    // * 16 because 256 (byte) / 16 (4 bits) = 16
                    colorOne.R = (byte)((twoColours      & 0xF) * 16);
                    colorOne.G = (byte)((twoColours >> 4 & 0xF) * 16);
                    colorOne.B = (byte)((twoColours >> 8 & 0xF) * 16);

                    colorTwo.R = (byte)((twoColours >> 12 & 0xF) * 16);
                    colorTwo.G = (byte)((twoColours >> 16 & 0xF) * 16);
                    colorTwo.B = (byte)((twoColours >> 20 & 0xF) * 16);

                    StarFoxPallettes.pallettes[p, c]     = colorOne;
                    StarFoxPallettes.pallettes[p, c + 1] = colorTwo;

                    index += 3;
                }
            }

            StarFoxPallettes.currentPallette = 0;
        }

        private int parseVertices(int index, StarFoxGeometry arwing)
        {
            while (rom.romData[index] != END_OF_VERTICES_FLAG)
            {
                uint vertexListType = rom.romData[index];
                index += 1;

                uint numberOfVertices = rom.romData[index];
                index += 1;

                Debug.WriteLine("list type:{0:X} number of verts:{1:D}", vertexListType, numberOfVertices);

                for (int v = 0; v < numberOfVertices; v++)
                {
                    //implicit cast should be ok: http://msdn.microsoft.com/en-us/library/x0sksh43(v=vs.71).aspx
                    float x = (sbyte)rom.romData[index++];
                    float y = (sbyte)rom.romData[index++];
                    float z = (sbyte)rom.romData[index++];

                    arwing.vertices.Add(new Vector3(x, y, z));
                }

                //todo: there are other vertexlisttypes
                //add N more mirrored points
                if (vertexListType == MIRROR_X_VERT_LIST)
                {
                    index = generateHorizontalMirroredPoints(index, numberOfVertices, arwing);
                }
            }
            return index;
        }
        internal int generateHorizontalMirroredPoints(int index, uint numberOfVertices, StarFoxGeometry arwing)
        {
            Debug.WriteLine("writing {0:D} mirrored verts", numberOfVertices);

            //reset index
            index -= 3 * (int)numberOfVertices;

            //Vector3 mirrorpoint = new Vector3();
            for (int v = 0; v < numberOfVertices; v++)
            {
                float x = (sbyte)rom.romData[index++];
                float y = (sbyte)rom.romData[index++];
                float z = (sbyte)rom.romData[index++];

                /*
                if (v == 0)
                {
                    mirrorpoint = new Vector3(x, y, z);
                    arwing.vertices.Add(mirrorpoint);
                }
                else
                {
                    Vector3 reflectedPoint = new Vector3(mirrorpoint.X - x, y, z);
                    arwing.vertices.Add(reflectedPoint);
                }
                */

                Vector3 reflectedPoint = new Vector3(-x, y, z);
                arwing.vertices.Add(reflectedPoint);
            }

            return index;
        }
        private int parseTriangles(int index, StarFoxGeometry arwing)
        {

            //index currently points to end of vertices flag...
            //so find triangles 
            while (rom.romData[index++] != BEGIN_TRIANGLES_FLAG) ;

            //write triangle data into previously defined arwinggon data.
            while (rom.romData[index] != BEGIN_BSP_FLAG)
            {
                uint numberOfTriangles = rom.romData[index];
                index += 1;

                Debug.WriteLine("number of triangles {0:D}", numberOfTriangles);

                for (int t = 0; t < numberOfTriangles; t++)
                {
                    uint p1 = rom.romData[index++];
                    uint p2 = rom.romData[index++];
                    uint p3 = rom.romData[index++];

                    arwing.triangles.Add(new SFGTriangle(p1, p2, p3));
                }
            }

            return index;
        }
        private int parseFacesFromBSP(int index, StarFoxGeometry arwing)
        {
            //index currently points to the bsp flag
            //so advance past
            while (rom.romData[index++] != BEGIN_BSP_FLAG) ;

            //BSP is not, and may never be, implemented
            //but we can use the tree to extract the correct number of faces (AFAICT there is no reliable sentinal for object data);
            while (rom.romData[index] != BEGIN_FACE_GROUP)
            {
                //the current byte contains the bsp flag
                uint node = rom.romData[index];

                if (node == BRANCH_NODE) index += 2; //skip triangle byte since we don't need plane data
                else if (node == LEAF_NODE) index += 1;

                //it's ok casting up unsigned to unsigned
                uint faceOffset = (uint)BitConverter.ToUInt16(rom.romData, index);
                index += 2;
                Debug.WriteLine("extracting faces from:{0:X} + {1:X}", faceOffset, index);
                faceOffset += (uint)index;

                SFGFaceGroup group = new SFGFaceGroup();

                uint numberOfSides = rom.romData[faceOffset];
                faceOffset += 1;

                Debug.WriteLine("number of sides:{0:X}", numberOfSides);

                group.number = rom.romData[faceOffset];
                faceOffset += 1;

                group.colour = rom.romData[faceOffset];
                faceOffset += 1;

                float xn = (sbyte)rom.romData[faceOffset++];
                float yn = (sbyte)rom.romData[faceOffset++];
                float zn = (sbyte)rom.romData[faceOffset++];
                group.normal = new Vector3(xn, yn, zn);

                for (int i = 0; i < numberOfSides; i++)
                    group.vertexIndeces.Add(rom.romData[faceOffset++]);

                arwing.faces.Add(group);

                //we read the leaves of the tree flat instead of traversing it
                //so we can skip rest of branch data
                if (node == BRANCH_NODE) index += 1;
            }

            return index;
        }
        #endregion helpers
    }
}
